package com.lufan.service;

import com.google.gson.Gson;
import com.lufan.data.dto.DownloadInfo;
import com.lufan.data.dto.VideoDownLoadDto;
import com.lufan.data.entity.VideoBasicInfoEntity;
import com.lufan.data.vo.VideoDownLoadVo;
import com.lufan.enums.ApiEnum;
import com.lufan.factory.HttpClientFactory;
import com.lufan.util.HttpUtil;
import com.lufan.util.MixAudioAndVideoUtil;
import com.lufan.util.UserContext;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
@Service
public class VideoDownloadService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate<String, DownloadInfo> downloadInfoRedisTemplate;
    private static final Gson gson = new Gson();
    private final OkHttpClient client = HttpClientFactory.getHttpClient();
    private ExecutorService executorService;
    private final Map<String, DownloadInfo> downloadInfos = new ConcurrentHashMap<>();
    @Value("${download.path}")
    private String downloadPath;
    private final int DEFALE_BUFFER_SIZE = 1024 * 10;
    @Autowired
    private MixAudioAndVideoUtil mixAudioAndVideoUtil;

    @PostConstruct
    public void init() {
        this.executorService = Executors.newFixedThreadPool(5);
        String resolvedDownloadPath = getResolvedDownloadPath();
        File dir = new File(resolvedDownloadPath);
        if (!dir.exists()) {
            boolean created = dir.mkdirs();
            if (created) {
                log.info("成功创建下载目录: {}", resolvedDownloadPath);
            } else {
                log.error("创建下载目录失败: {}", resolvedDownloadPath);
            }
        } else {
            log.info("下载目录已存在: {}", resolvedDownloadPath);
        }
    }

    public void downloadVideo(VideoDownLoadDto.VideoInfo video) {
        String downloadId = video.getBvid() + "_" + video.getCid();
        String currentSessdata = UserContext.getSessdata();
        DownloadInfo info = new DownloadInfo();
        info.setId(downloadId);
        info.setBvid(video.getBvid());
        info.setCid(video.getCid());
        setVideoBasicInfo(info, video.getBvid(), video.getCid());
        info.setStatus("preparing");
        info.setProgress(0);
        info.setCreateTime(System.currentTimeMillis());
        info.setUpdateTime(System.currentTimeMillis());
        info.setQuality(video.getQn().toString());
        info.setFormat("mp4");
        // 初始化下载统计字段
        info.setFileSize(0L);
        info.setDownloadedSize(0L);
        info.setSpeed(0L);
        info.setRemainingTime(0L);
        downloadInfos.put(downloadId, info);
        downloadInfoRedisTemplate.opsForHash().put("download", downloadId, info);
        log.info("创建下载进度信息: {} - 标题: {} - 当前总任务数: {}", downloadId, info.getTitle(), downloadInfos.size());
        CompletableFuture.runAsync(() -> {
            try {
                if (currentSessdata != null) {
                    UserContext.setSessdata(currentSessdata);
                    // 传递 SESSDATA
                    log.info("在异步线程中传递SESSDATA: {}", currentSessdata.substring(0, Math.min(10, currentSessdata.length())) + "...");
                } else {
                    log.warn("没有SESSDATA可以恢复到异步线程");
                }
                VideoDownLoadVo videoInfo = getVideoInfo(video);
                VideoBasicInfoEntity mainVideoInfo = getMainVideoInfo(video.getBvid());
                String mainTitle = mainVideoInfo.getData().getTitle();
                if (mainTitle == null || mainTitle.trim().isEmpty()) {
                    mainTitle = "视频下载_" + video.getBvid();
                }
                String folderName = sanitizeFileName(mainTitle);
                String filePrefix;
                if (mainVideoInfo.getData().getPages() != null && mainVideoInfo.getData().getPages().size() > 1) {
                    String partTitle = info.getTitle();
                    if (partTitle == null || partTitle.trim().isEmpty()) {
                        for (VideoBasicInfoEntity.Page page : mainVideoInfo.getData().getPages()) {
                            if (video.getCid().equals(String.valueOf(page.getCid()))) {
                                partTitle = page.getPart();
                                break;
                            }
                        }
                        if (partTitle == null || partTitle.trim().isEmpty()) {
                            partTitle = "P" + video.getCid();
                        }
                    }
                    filePrefix = sanitizeFileName(partTitle);
                } else {
                    filePrefix = folderName;
                }
                String resolvedDownloadPath = getResolvedDownloadPath();
                String downloadDir = resolvedDownloadPath + File.separator + folderName;
                Path dirPath = Paths.get(downloadDir);
                if (!Files.exists(dirPath)) {
                    Files.createDirectories(dirPath);
                    log.info("创建下载目录: {}", downloadDir);
                }
                String videoPath = downloadDir + File.separator + filePrefix + "_video.mp4";
                String audioPath = downloadDir + File.separator + filePrefix + "_audio.mp4";
                String outputPath = downloadDir + File.separator + filePrefix + ".mp4";
                info.setStatus("downloading");
                downloadFile(videoInfo, videoPath, downloadId, "video");
                info.setStatus("正在下载音频");
                downloadFile(videoInfo, audioPath, downloadId, "audio");
                info.setStatus("正在合并文件");
                mergeVideoAndAudio(videoPath, audioPath, outputPath, downloadId);
                new File(videoPath).delete();
                new File(audioPath).delete();
                info.setStatus("completed");
                info.setProgress(100);
                info.setPath(outputPath);
                info.setUpdateTime(System.currentTimeMillis());
                // 重置实时统计信息，只保留总文件大小
                info.setDownloadedSize(info.getFileSize());
                info.setSpeed(0L);
                info.setRemainingTime(0L);
                info.setProgress(100);
                log.info("下载任务完成: {} - 文件路径: {}", downloadId, outputPath);
            } catch (Exception e) {
                log.error("下载视频失败: {}", downloadId, e);
                info.setStatus("failed");
                info.setError(e.getMessage());
                info.setUpdateTime(System.currentTimeMillis());
            } finally {
                downloadInfoRedisTemplate.opsForHash().put("download", downloadId, info);
                UserContext.clear();
            }
        }, executorService);
    }


    private VideoDownLoadVo getVideoInfo(VideoDownLoadDto.VideoInfo video) {
        try {
            log.info("获取视频信息: bvid={}, cid={}", video.getBvid(), video.getCid());
            long cid;
            try {
                cid = Long.parseLong(video.getCid());
            } catch (NumberFormatException e) {
                log.error("无效的 cid 格式: {}", video.getCid());
                throw new IllegalArgumentException("无效的 cid 格式: " + video.getCid());
            }
            String url = ApiEnum.VIDEO_STREAMING.getUrl() +
                    "?bvid=" + video.getBvid() +
                    "&cid=" + cid +
                    "&qn=" + (video.getQn() != null ? video.getQn() : 80) +
                    "&fnval=" + (video.getFnval() != null ? video.getFnval() : 16) +
                    "&fnver=0&fourk=1";
            Response response = HttpUtil.sendGet(client, url);
            if (!response.isSuccessful()) {
                throw new RuntimeException("获取视频信息失败");
            }
            String responseBody = null;
            if (response.body() != null) {
                responseBody = response.body().string();
            }
            return gson.fromJson(responseBody, VideoDownLoadVo.class);
        } catch (Exception e) {
            throw new RuntimeException("获取视频信息失败: " + e.getMessage());
        }
    }

    private void downloadFile(VideoDownLoadVo videoData, String filePath, String downloadId, String type) {
        try {
            log.info("开始下载{}文件: {}", type, filePath);
            DownloadInfo info = downloadInfos.get(downloadId);
            if (videoData == null || videoData.getData() == null) {
                throw new RuntimeException("无效的视频信息");
            }
            // 获取下载URL和文件大小
            String downloadUrl = "";
            long contentLength = 0;
            if (type.equals("video")) {
                if (videoData.getData().getDash() != null) {
                    for (VideoDownLoadVo.Video video : videoData.getData().getDash().getVideo()) {
                        // 默认获取 1080p视频
                        if (video.getId() == 80) {
                            downloadUrl = video.getBaseUrl();
                            break;
                        }
                    }
                } else {
                    throw new RuntimeException("未找到视频下载地址");
                }
            } else {
                if (videoData.getData().getDash() != null && videoData.getData().getDash().getAudio() != null
                        && !videoData.getData().getDash().getAudio().isEmpty()) {
                    VideoDownLoadVo.Audio audioData = videoData.getData().getDash().getAudio().get(0);
                    downloadUrl = audioData.getBaseUrl();
                } else {
                    throw new RuntimeException("未找到音频下载地址");
                }
            }

            if (downloadUrl == null || downloadUrl.isEmpty()) {
                throw new RuntimeException("未找到" + type + "下载地址");
            }
            //通过HEAD请求获取视频大小
            if (contentLength == 0) {
                try {
                    Request headRequest = new Request.Builder()
                            .url(downloadUrl)
                            .head()
                            .addHeader("Referer", "https://www.bilibili.com")
                            .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
                            .build();
                    try (Response headResponse = client.newCall(headRequest).execute()) {
                        if (headResponse.isSuccessful()) {
                            String contentLengthHeader = headResponse.header("Content-Length");
                            if (contentLengthHeader != null) {
                                contentLength = Long.parseLong(contentLengthHeader);
                            }
                        }
                    }
                } catch (Exception e) {
                    log.warn("无法获取文件大小: {}", e.getMessage());
                }
            }
            downloadFileWithProgress(downloadUrl, filePath, downloadId, type, info.getFileSize());
        } catch (Exception e) {
            log.error("下载{}文件失败: {}", type, filePath, e);
            throw new RuntimeException("下载" + type + "文件失败: " + e.getMessage());
        }
    }


    private void downloadFileWithProgress(String downloadUrl, String filePath, String downloadId, String type, long contentLength) throws IOException {
        DownloadInfo info = downloadInfos.get(downloadId);
        Request request = new Request.Builder()
                .url(downloadUrl)
                .addHeader("Referer", "https://www.bilibili.com")
                .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new RuntimeException("下载" + type + "文件失败: HTTP " + response.code());
            }
            if (contentLength == 0) {
                String contentLengthHeader = response.header("Content-Length");
                if (contentLengthHeader != null) {
                    contentLength = Long.parseLong(contentLengthHeader);
                }
            }
            try (InputStream inputStream = response.body().byteStream();
                 FileOutputStream outputStream = new FileOutputStream(filePath)) {
                byte[] buffer = new byte[DEFALE_BUFFER_SIZE];
                long downloadedBytes = 0;
                long startTime = System.currentTimeMillis();
                long lastUpdateTime = startTime;
                int bytesRead;
                // 初始进度更新
                info.setDownloadedSize(0);
                info.setSpeed(0);
                if (contentLength > 0) {
                    info.setRemainingTime(0);
                }
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                    downloadedBytes += bytesRead;
                    long currentTime = System.currentTimeMillis();
                    long timeDiff = currentTime - lastUpdateTime;
                    // 每500ms更新一次进度
                    if (timeDiff >= 500) {
                        // 计算下载速度（字节/秒）
                        long totalTime = currentTime - startTime;
                        long speed = totalTime > 0 ? (downloadedBytes * 1000 / totalTime) : 0;
                        // 计算剩余时间（秒）
                        long remainingTime = 0;
                        if (contentLength > 0 && speed > 0) {
                            long remainingBytes = contentLength - downloadedBytes;
                            remainingTime = remainingBytes / speed;
                        }
                        // 计算进度百分比
                        int progress = info.getProgress();
                        if (contentLength > 0) {
                            int fileProgress = (int) ((downloadedBytes * 100) / contentLength);
                            progress = fileProgress;
                        }
                        info.setProgress(progress);
                        info.setDownloadedSize(downloadedBytes);
                        info.setSpeed(speed);
                        info.setRemainingTime(remainingTime);
                        info.setUpdateTime(currentTime);
                        lastUpdateTime = currentTime;


                    }
                }
                // 最终更新
                long totalTime = System.currentTimeMillis() - startTime;
                long avgSpeed = totalTime > 0 ? (downloadedBytes * 1000 / totalTime) : 0;
                int finalProgress = 100;
                info.setProgress(finalProgress);
                info.setDownloadedSize(downloadedBytes);
                info.setSpeed(avgSpeed);
                info.setRemainingTime(0);
                info.setUpdateTime(System.currentTimeMillis());
            }
        }
    }

    private void mergeVideoAndAudio(String videoPath, String audioPath, String outputPath, String downloadId) {
        try {
            log.info("开始合并视频和音频: {}", outputPath);
            // 使用 FFmpeg 合并视频和音频
            String mergedPath = mixAudioAndVideoUtil.muxVideoAndAudio(videoPath, audioPath, outputPath);
            log.info("视频和音频合并完成: {}", mergedPath);
        } catch (Exception e) {
            log.error("合并视频和音频失败: {}", outputPath, e);
            throw new RuntimeException("合并视频和音频失败: " + e.getMessage());
        }
    }

    public List<DownloadInfo> getDownloadList() {
        if (downloadInfos.size() == 0) {
            forceUpdateCache();
        }
        return new ArrayList<>(downloadInfos.values());
    }

    private void forceUpdateCache() {
        downloadInfos.clear();
        // 从缓存获取
        Map<Object, Object> download = downloadInfoRedisTemplate.opsForHash().entries("download");
        for (Map.Entry<Object, Object> entry : download.entrySet()) {
            String key = entry.getKey().toString();
            DownloadInfo info = (DownloadInfo) entry.getValue();
            downloadInfos.put(key, info);
        }
    }

    public void retryDownload(String id) {
        DownloadInfo info = downloadInfos.get(id);
        if (info != null && "failed".equals(info.getStatus())) {
            VideoDownLoadDto.VideoInfo video = new VideoDownLoadDto.VideoInfo();
            video.setBvid(info.getBvid());
            // 从downloadId中解析cid
            String[] parts = id.split("_");
            if (parts.length >= 2) {
                video.setCid(parts[1]);
            }
            downloadVideo(video);
        }
    }


    public void deleteDownload(String downloadId) {
        log.info("删除下载任务: {}", downloadId);
        // 删除 redis
        downloadInfoRedisTemplate.opsForHash().delete("download", downloadId);
        // 强制更新缓存
        forceUpdateCache();
    }

    public void clearCompletedDownloads() {
        downloadInfos.clear();
        Map<Object, Object> download = downloadInfoRedisTemplate.opsForHash().entries("download");
        for (Map.Entry<Object, Object> entry : download.entrySet()) {
            String key = entry.getKey().toString();
            DownloadInfo info = (DownloadInfo) entry.getValue();
            if (!Objects.equals(info.getStatus(), "completed")) {
                downloadInfos.put(key, info);
            } else {
                downloadInfoRedisTemplate.opsForHash().delete("download", key);
            }
        }
    }

    private boolean isDownloading(String status) {
        return "preparing".equals(status) || "downloading".equals(status) ||
                "正在下载".equals(status) || "正在下载音频".equals(status) ||
                "正在合并文件".equals(status);
    }

    /**
     * 设置视频基本信息（标题、封面等）
     *
     * @param info 下载信息对象
     * @param bvid 视频BV号
     * @param cid  视频CID
     */
    private void setVideoBasicInfo(DownloadInfo info, String bvid, String cid) {
        try {
            log.info("获取视频基本信息: bvid={}, cid={}", bvid, cid);
            // 构建请求URL
            String url = ApiEnum.VIDEO_BASIC_INFO.getUrl() + "?bvid=" + bvid;
            log.info("请求视频基本信息URL: {}", url);
            // 发送请求
            Response response = HttpUtil.sendGet(client, url);
            if (!response.isSuccessful()) {
                log.error("获取视频基本信息失败: HTTP {}", response.code());
                info.setTitle("视频下载 - " + bvid + " (P" + cid + ")"); // 降级标题
                return;
            }

            String responseBody = response.body().string();
            log.debug("视频基本信息响应: {}", responseBody);

            // 解析响应
            VideoBasicInfoEntity videoBasicInfo = gson.fromJson(responseBody, VideoBasicInfoEntity.class);
            if (videoBasicInfo == null || videoBasicInfo.getCode() != 0) {
                log.error("解析视频基本信息失败: {}", responseBody);
                info.setTitle("视频下载 - " + bvid + " (P" + cid + ")"); // 降级标题
                return;
            }

            // 设置封面
            if (videoBasicInfo.getData().getPic() != null && !videoBasicInfo.getData().getPic().trim().isEmpty()) {
                info.setCover(videoBasicInfo.getData().getPic());
            }
            // 获取并设置标题
            String title = videoBasicInfo.getData().getTitle();
            if (title == null || title.trim().isEmpty()) {
                log.warn("视频标题为空，使用默认标题");
                info.setTitle("视频下载 - " + bvid + " (P" + cid + ")"); // 降级标题
                return;
            }
            // 如果是多P视频，需要获取具体分P的标题
            if (videoBasicInfo.getData().getPages() != null && videoBasicInfo.getData().getPages().size() > 1) {
                for (VideoBasicInfoEntity.Page page : videoBasicInfo.getData().getPages()) {
                    if (cid.equals(String.valueOf(page.getCid()))) {
                        String partTitle = page.getPart();
                        if (partTitle != null && !partTitle.trim().isEmpty()) {
                            info.setTitle(partTitle);//分P标题
                        } else {
                            info.setTitle(title); // 只有主标题
                        }
                        return;
                    }
                }
            }

            info.setTitle(title); // 设置主标题

        } catch (Exception e) {
            log.error("获取视频基本信息失败: bvid={}, cid={}", bvid, cid, e);
            info.setTitle("视频下载 - " + bvid + " (P" + cid + ")"); // 降级标题
        }
    }

    /**
     * 清理文件名中的非法字符
     *
     * @param fileName 原始文件名
     * @return 清理后的安全文件名
     */
    private String sanitizeFileName(String fileName) {
        if (fileName == null || fileName.trim().isEmpty()) {
            return "未知视频";
        }

        // 移除或替换Windows和其他系统中的非法字符
        String sanitized = fileName
                .replaceAll("[\\\\/:*?\"<>|]", "_")  // 替换非法字符为下划线
                .replaceAll("\\s+", " ")            // 多个空格替换为单个空格
                .trim();                            // 去除首尾空格

        // 限制文件名长度，避免路径过长
        if (sanitized.length() > 100) {
            sanitized = sanitized.substring(0, 100) + "...";
        }

        // 确保文件名不为空
        if (sanitized.isEmpty()) {
            sanitized = "未知视频";
        }

        log.debug("文件名清理: {} -> {}", fileName, sanitized);
        return sanitized;
    }

    /**
     * 获取主视频信息
     *
     * @param bvid 视频BVID
     * @return 视频基本信息
     */
    private VideoBasicInfoEntity getMainVideoInfo(String bvid) {
        try {
            String url = ApiEnum.VIDEO_BASIC_INFO.getUrl() + "?bvid=" + bvid;
            Response response = HttpUtil.sendGet(client, url);
            if (!response.isSuccessful()) {
                throw new RuntimeException("获取视频基本信息失败: HTTP " + response.code());
            }

            String responseBody = response.body().string();
            VideoBasicInfoEntity videoBasicInfo = gson.fromJson(responseBody, VideoBasicInfoEntity.class);
            if (videoBasicInfo == null || videoBasicInfo.getCode() != 0) {
                throw new RuntimeException("解析视频基本信息失败");
            }
            return videoBasicInfo;
        } catch (Exception e) {
            log.error("获取主视频信息失败: bvid={}", bvid, e);
            throw new RuntimeException("获取主视频信息失败: " + e.getMessage());
        }
    }

    /**
     * 解析下载路径，替换环境变量
     *
     * @return 解析后的绝对路径
     */
    private String getResolvedDownloadPath() {
        return downloadPath.replace("${user.home}", System.getProperty("user.home"));
    }
}
